from typing import Dict, List, Optional
import pandas as pd
import numpy as np
from .strategy_base import StrategyBase
from ..data.data_processor import DataProcessor

class Backtester:
    def __init__(
        self,
        strategy: StrategyBase,
        initial_capital: float,
        commission: float = 0.001
    ):
        self.strategy = strategy
        self.initial_capital = initial_capital
        self.commission = commission
        self.positions: Dict[str, float] = {}
        self.cash: float = initial_capital
        self.portfolio_value: float = initial_capital
        self.trades: List[Dict] = []

    def run(self, data: pd.DataFrame) -> Dict:
        """
        Run backtest on historical data
        """
        # Clean and prepare data
        data = DataProcessor.clean_data(data)
        data = DataProcessor.add_technical_indicators(data)
        
        # Generate trading signals
        signals = self.strategy.generate_signals(data)
        
        # Initialize results tracking
        portfolio_values = []
        positions = []
        
        for i in range(len(data)):
            date = data.index[i]
            current_price = data['Close'].iloc[i]
            signal = signals.iloc[i]
            
            # Execute trades based on signals
            if signal == 1 and self.cash > 0:  # Buy signal
                shares = self._calculate_position_size(self.cash, current_price)
                cost = shares * current_price * (1 + self.commission)
                if cost <= self.cash:
                    self.positions[date] = shares
                    self.cash -= cost
                    self.trades.append({
                        'date': date,
                        'type': 'buy',
                        'shares': shares,
                        'price': current_price,
                        'cost': cost
                    })
            
            elif signal == -1 and self.positions:  # Sell signal
                shares = sum(self.positions.values())
                revenue = shares * current_price * (1 - self.commission)
                self.cash += revenue
                self.positions.clear()
                self.trades.append({
                    'date': date,
                    'type': 'sell',
                    'shares': shares,
                    'price': current_price,
                    'revenue': revenue
                })
            
            # Track portfolio value
            position_value = sum(self.positions.values()) * current_price
            self.portfolio_value = self.cash + position_value
            portfolio_values.append(self.portfolio_value)
            positions.append(position_value)
        
        # Calculate performance metrics
        returns = pd.Series(portfolio_values).pct_change()
        
        results = {
            'final_portfolio_value': self.portfolio_value,
            'total_return': (self.portfolio_value - self.initial_capital) / self.initial_capital,
            'sharpe_ratio': self._calculate_sharpe_ratio(returns),
            'max_drawdown': self._calculate_max_drawdown(portfolio_values),
            'trades': self.trades,
            'portfolio_values': portfolio_values,
            'positions': positions
        }
        
        return results

    def _calculate_position_size(self, available_cash: float, price: float) -> float:
        """
        Calculate the number of shares to buy based on available cash
        """
        return int(available_cash * 0.95 / (price * (1 + self.commission)))

    def _calculate_sharpe_ratio(self, returns: pd.Series, risk_free_rate: float = 0.01) -> float:
        """
        Calculate the Sharpe ratio of the strategy
        """
        excess_returns = returns - risk_free_rate/252  # Assuming daily returns
        if len(excess_returns) < 2:
            return 0.0
        return np.sqrt(252) * excess_returns.mean() / excess_returns.std()

    def _calculate_max_drawdown(self, portfolio_values: List[float]) -> float:
        """
        Calculate the maximum drawdown of the strategy
        """
        peak = portfolio_values[0]
        max_drawdown = 0.0
        
        for value in portfolio_values:
            if value > peak:
                peak = value
            drawdown = (peak - value) / peak
            max_drawdown = max(max_drawdown, drawdown)
        
        return max_drawdown 